package biz

import (
	"context"
	"time"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"gorm.io/gorm"

	pb "gitee.com/danqingzhao/go-dango/api/user/menu/v1"
	"gitee.com/danqingzhao/go-dango/pkg/common/core"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
	"gitee.com/danqingzhao/go-dango/pkg/third/orm"
)

type MenuModel struct {
	Id           uint32            `gorm:"column:id;primary_key;AUTO_INCREMENT;comment:编号" json:"id"`
	CreatedAt    time.Time         `gorm:"column:create_time;comment:创建时间" json:"created_at"`
	UpdatedAt    time.Time         `gorm:"column:update_time;comment:修改时间" json:"updated_at"`
	DeleteAt     gorm.DeletedAt    `gorm:"column:delete_at;type:boolean;comment:是否删除" json:"delete_at"`
	Path         string            `gorm:"column:path;type:varchar(100);not null;uniqueIndex;comment:前端路由" json:"url"`
	Component    string            `gorm:"column:component;type:varchar(200);not null;comment:请求方式" json:"method"`
	Name         string            `gorm:"column:name;type:varchar(50);not null;uniqueIndex;comment:名称" json:"name"`
	AppName      string            `gorm:"column:app_name;type:varchar(50);not null;comment:应用名称" json:"app_name"`
	Meta         string            `gorm:"column:meta;comment:菜单信息" json:"meta"`
	ArrangeOrder uint32            `gorm:"column:arrange_order;type:integer;comment:排序" json:"arrange_order"`
	IsActive     bool              `gorm:"column:is_active;type:boolean;comment:是否激活" json:"is_active"`
	Descr        string            `gorm:"column:descr;type:varchar(254);comment:描述" json:"descr"`
	ParentId     *uint32           `gorm:"column:parent_id;foreignKey:ParentId;references:Id;constraint:OnDelete:CASCADE;comment:菜单" json:"parent"`
	Parent       *MenuModel        `gorm:"foreignKey:ParentId;constraint:OnDelete:CASCADE"`
	Permissions  []PermissionModel `gorm:"many2many:auth_menu_permission;joinForeignKey:menu_id;joinReferences:permission_id;constraint:OnDelete:CASCADE"`
}

func (m *MenuModel) TableName() string {
	return "user_menu"
}

type MenuRepo interface {
	CreateModel(context.Context, *MenuModel) error
	UpdateModel(context.Context, MenuModel, map[string]any, ...any) error
	DeleteModel(context.Context, ...any) error
	FindModel(context.Context, []string, ...any) (*MenuModel, error)
	ListModel(context.Context, orm.QueryParams) (int64, []MenuModel, error)
	ListPolicies(context.Context, MenuModel) ([][]string, [][]string)
}

type MenuUsecase struct {
	log        *log.Helper
	permRepo   PermissionRepo
	menuRepo   MenuRepo
	policyRepo PolicyRepo
}

func NewMenuUsecase(
	logHelpers *settings.LogHelpers,
	permRepo PermissionRepo,
	menuRepo MenuRepo,
	policyRepo PolicyRepo,
) *MenuUsecase {
	return &MenuUsecase{
		log:        logHelpers.Biz,
		permRepo:   permRepo,
		menuRepo:   menuRepo,
		policyRepo: policyRepo,
	}
}

func (uc *MenuUsecase) association(
	ctx context.Context,
	m MenuModel,
	pid uint32,
	permIds []uint32,
) (*MenuModel, *errors.Error) {
	var parentId *uint32 = nil
	if pid != 0 {
		m, fErr := uc.menuRepo.FindModel(ctx, nil, pid)
		if fErr != nil {
			rErr := core.NewGormError(fErr, pb.ErrorReason_MENU_FIND_PARENT_ERROR.String(), core.GenerateTempDataByID(pid))
			uc.log.WithContext(ctx).Error(rErr.Error())
			return nil, rErr
		}
		parentId = &m.Id
	}
	m.ParentId = parentId
	qp := orm.QueryParams{
		Preloads: []string{},
		Query:    map[string]any{"id in ?": permIds},
		OrderBy:  []string{"id"},
		IsCount:  false,
		Limit:    0,
		Offset:   0,
	}
	_, pms, lErr := uc.permRepo.ListModel(ctx, qp)
	if lErr != nil {
		rErr := core.NewGormError(lErr, pb.ErrorReason_MENU_LIST_PERMISSION_ERROR.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if len(pms) > 0 {
		m.Permissions = pms
	}
	return &m, nil
}

func (uc *MenuUsecase) CreateMenu(
	ctx context.Context,
	pid uint32,
	permIds []uint32,
	m MenuModel,
) (*MenuModel, *errors.Error) {
	am, aErr := uc.association(ctx, m, pid, permIds)
	if aErr != nil {
		return nil, aErr
	}
	if err := uc.menuRepo.CreateModel(ctx, am); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_MENU_CREATE_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	pRules, gRules := uc.menuRepo.ListPolicies(ctx, *am)
	if err := uc.policyRepo.AddPolicies(ctx, pRules, gRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_MENU_ADD_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return am, nil
}

func (uc *MenuUsecase) UpdateMenuById(
	ctx context.Context,
	menuId, pid uint32,
	permIds []uint32,
	im MenuModel,
) (*MenuModel, *errors.Error) {
	am, aErr := uc.association(ctx, im, pid, permIds)
	if aErr != nil {
		return nil, aErr
	}
	upmap := make(map[string]any)
	if am.Permissions != nil {
		upmap["Permissions"] = am.Permissions
	}
	om, fErr := uc.FindMenuByID(ctx, []string{"Permissions"}, menuId)
	if fErr != nil {
		return nil, fErr
	}
	pOldRules, gOldRules := uc.menuRepo.ListPolicies(ctx, *om)
	if err := uc.menuRepo.UpdateModel(ctx, *am, upmap, "id = ?", menuId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_MENU_UPDATE_FAILED.String(), core.GenerateTempDataByID(menuId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	pNewRules, gNewRules := uc.menuRepo.ListPolicies(ctx, *am)
	if err := uc.policyRepo.RemovePolicies(ctx, pOldRules, gOldRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_MENU_REMOVE_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if err := uc.policyRepo.AddPolicies(ctx, pNewRules, gNewRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_MENU_ADD_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return uc.FindMenuByID(ctx, []string{"Permissions"}, menuId)
}

func (uc *MenuUsecase) DeleteMenuById(ctx context.Context, menuId uint32) *errors.Error {
	m, fErr := uc.FindMenuByID(ctx, []string{"Permissions"}, menuId)
	if fErr != nil {
		return fErr
	}
	pRules, gRules := uc.menuRepo.ListPolicies(ctx, *m)
	if err := uc.menuRepo.DeleteModel(ctx, menuId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_MENU_DELETE_FAILED.String(), core.GenerateTempDataByID(menuId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	if err := uc.policyRepo.RemovePolicies(ctx, pRules, gRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_MENU_REMOVE_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	return nil
}

func (uc *MenuUsecase) FindMenuByID(
	ctx context.Context,
	preloads []string,
	menuId uint32,
) (*MenuModel, *errors.Error) {
	m, err := uc.menuRepo.FindModel(ctx, preloads, menuId)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_MENU_FIND_FAILED.String(), core.GenerateTempDataByID(menuId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return m, nil
}

func (uc *MenuUsecase) ListMenu(
	ctx context.Context,
	preloads []string,
	page, size int,
	query map[string]any,
) (int64, []MenuModel, *errors.Error) {
	qp := orm.QueryParams{
		Preloads: []string{},
		Query:    query,
		OrderBy:  []string{"id"},
		Limit:    max(size, 0),
		Offset:   max(page-1, 0),
		IsCount:  true,
	}
	count, ms, err := uc.menuRepo.ListModel(ctx, qp)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_MENU_LIST_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return 0, nil, rErr
	}
	return count, ms, nil
}
